<template>
    <div class="VirtualScroll-container"
        @mouseenter="pause"
        @mouseleave="play"
        v-resize="setParentSize">

        <div :class="['recycle-wrap', needAnimation && animationClass]"
            @animationiteration="onAnimationiteration"
            :style="{
            display: 'flex',
            animationPlayState: playState,
            animationDuration: duration,
            ...styles.recycleWrap,
        }">
            <div class="recycle-item"
                :style="{
            flex: `0 0 ${handleCssUnit(itemSize)}`,
            ...styles.recycleItem
        }"
                v-for="(item, index) in itemPool"
                :key="item[keyField] ?? index">

                <slot :item="item">
                    请在默认插槽下使用数据`item`:
                    {{ item }}
                </slot>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts" generic="T extends {
    src?: string
    [K: string]: any
}">
import { handleCssUnit } from '@jl-org/tool'
import type { ResizeFn } from '@/directives/resize'


/**
 * 虚拟自动滚动组件，根据大小
 * 动态加载可视范围的数据，防止卡顿
 * 使用 动画切割 实现
 */
defineOptions({
    inheritAttrs: true,
    name: "VirtualScroll",
})
const props = withDefaults(
    defineProps<{
        data: T[]
        dir?: 'x' | 'y'
        /** 
         * 高度或者宽度 根据`dir`决定  
         * 这个数值必须能被父容器大小 **整除**
         */
        itemSize?: number
        speed?: number
        keyField?: string
    }>(),
    {
        itemSize: 100,
        speed: 40,
        dir: 'x',
        keyField: 'id',
    }
)


const
    /** 子元素大小总和 */
    totalSize = computed(() => {
        const len = props.data.length
        return props.itemSize * len
    }),

    parentSize = ref(0),
    setParentSize: ResizeFn = ({ width, height }) => {
        switch (props.dir) {
            case 'x':
                parentSize.value = width
                break
            case 'y':
                parentSize.value = height
                break

            default:
                break
        }

        if (parentSize.value % props.itemSize !== 0) {
            throw new Error('itemSize 必须能被父容器大小整除 (itemSize must be divisible by parentSize)')
        }
    },

    /** 一页展示的个数 */
    showCount = ref(0),
    /** 展示的大小，可能是高度，也可能是宽度 */
    showSize = computed(() => showCount.value * props.itemSize),
    /** 内容比容器大再滚动 */
    needAnimation = computed(() => totalSize.value > parentSize.value),
    playState = ref<'paused' | 'running'>('running'),
    animationClass = computed(() =>
        props.dir === 'x'
            ? 'animation-x'
            : 'animation-y'
    ),

    curIndex = ref(0),
    endIndex = computed(() => curIndex.value + showCount.value),
    /** 实际的展示数据 */
    itemPool = computed(() => props.data.slice(curIndex.value, endIndex.value)),

    duration = computed(() => getDuration(parentSize.value, props.speed) + 's')

const styles = computed(() => {
    if (props.dir === 'x') {
        return {
            recycleWrap: {
                flexDirection: 'row' as const,
                width: handleCssUnit(showSize.value)
            },
            recycleItem: {
                height: '100%',
                width: handleCssUnit(props.itemSize)
            }
        }
    }

    return {
        recycleWrap: {
            flexDirection: 'column' as const,
            height: handleCssUnit(showSize.value)
        },
        recycleItem: {
            width: '100%',
            height: handleCssUnit(props.itemSize)
        }
    }
})


/** --------------------------------------------------------------------
 * 初始化入口
 */
onMounted(() => {
    watch(
        [props, parentSize],
        setData,
        { immediate: true, deep: true }
    )
})


/** ---------------------------------------------------------------------------
 * 事件
 */
function onAnimationiteration() {
    setCurIndex()
}

function play() {
    playState.value = 'running'
}
function pause() {
    playState.value = 'paused'
}


/** -------------------------------------------------------------------------
 * 工具
 */
function setData() {
    if (parentSize.value > 0) {
        /** 展示两倍长度 再动态切换 */
        showCount.value = Math.ceil(parentSize.value / props.itemSize) * 2
    }
}

/** 设置动态索引 自动改变展示内容 */
function setCurIndex() {
    curIndex.value += Math.floor(showCount.value / 2)

    if (curIndex.value >= props.data.length - 1) {
        curIndex.value = 0
    }
}

/**
 * 返回滚动动画时间 单位：s
 * @param size 容器大小
 * @param speed 每秒滚动距离 单位: px
 */
function getDuration(size = 0, speed: number) {
    if (!size) return 0

    return size / speed
}

</script>

<style scoped lang="scss">
.VirtualScroll-container {
    @include full;
    overflow: hidden;
}

.recycle-wrap {
    @include flex();
    @include full;
}

.animation-x {
    animation: scrollX 3s infinite linear;
}

.animation-y {
    animation: scrollY 3s infinite linear;
}

@keyframes scrollX {
    100% {
        transform: translateX(-50%);
    }
}

@keyframes scrollY {
    100% {
        transform: translateY(-50%);
    }
}
</style>
